/*
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package hitool.core.lang3.wraper;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Enumeration;
import java.util.Iterator;

import hitool.core.lang3.iterator.EnumerationIterator;

/*
 * A class to wrap access to multiple class loaders making them work as one
 *
 * @author Clinton Begin
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class ClassLoaderWrapper {

	protected ClassLoader defaultClassLoader;
	protected ClassLoader contextClassLoader;

	public ClassLoaderWrapper() {
		try {
			contextClassLoader = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
				public Object run() {
					//Thread.currentThread().getContextClassLoader(),
				    return Thread.currentThread().getContextClassLoader();	
				}
			});
		} catch (SecurityException ignored) {
			// AccessControlException on Google App Engine
		}
	}

	/*
	 * Get a resource as a URL using the current class path
	 * 
	 * @param resource - the resource to locate
	 * 
	 * @return the resource or null
	 */
	public URL getResourceAsURL(String resource) {
		return getResourceAsURL(resource, getClassLoaders(null));
	}

	/*
	 * Get a resource from the classpath, starting with a specific class loader
	 * 
	 * @param resource - the resource to find
	 * 
	 * @param classLoader - the first classloader to try
	 * 
	 * @return the stream or null
	 */
	public URL getResourceAsURL(String resource, ClassLoader classLoader) {
		return getResourceAsURL(resource, getClassLoaders(classLoader));
	}
	
	public Iterator<URL> getResourceAsURLs(String resource, ClassLoader classLoader) throws IOException {
		return getResourceAsURLs(resource, getClassLoaders(classLoader));
	}

	/*
	 * Get a resource from the classpath
	 * 
	 * @param resource - the resource to find
	 * 
	 * @return the stream or null
	 */
	public InputStream getResourceAsStream(String resource) {
		return getResourceAsStream(resource, getClassLoaders(null));
	}

	/*
	 * Get a resource from the classpath, starting with a specific class loader
	 * 
	 * @param resource - the resource to find
	 * 
	 * @param classLoader - the first class loader to try
	 * 
	 * @return the stream or null
	 */
	public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
		return getResourceAsStream(resource, getClassLoaders(classLoader));
	}

	/*
	 * Find a class on the classpath (or die trying)
	 * 
	 * @param name - the class to look for
	 * 
	 * @return - the class
	 * 
	 * @throws ClassNotFoundException Duh.
	 */
	public Class<?> classForName(String name) throws ClassNotFoundException {
		return classForName(name, getClassLoaders(null));
	}

	/*
	 * Find a class on the classpath, starting with a specific classloader (or
	 * die trying)
	 * 
	 * @param name - the class to look for
	 * 
	 * @param classLoader - the first classloader to try
	 * 
	 * @return - the class
	 * 
	 * @throws ClassNotFoundException Duh.
	 */
	public Class<?> classForName(String name, ClassLoader classLoader) throws ClassNotFoundException {
		return classForName(name, getClassLoaders(classLoader));
	}

	/*
	 * Try to get a resource from a group of classloaders
	 * 
	 * @param resource - the resource to get
	 * 
	 * @param classLoader - the classloaders to examine
	 * 
	 * @return the resource or null
	 */
	InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
		
		InputStream returnValue = null;
		
		for (ClassLoader cl : classLoader) {
			if (null != cl) {

				// try to find the resource as passed
				returnValue = cl.getResourceAsStream(resource);

				// now, some class loaders want this leading "/", so we'll add
				// it and try again if we didn't find the resource
				if (null == returnValue) {
					returnValue = cl.getResourceAsStream('/' + resource);
				}

				if (null != returnValue) {
					return returnValue;
				}
			}
		}
		
		// try again if we didn't find the resource
		if (null == returnValue) {
			returnValue = ClassLoader.getSystemResourceAsStream(resource);
			if (null == returnValue) {
				returnValue = ClassLoader.getSystemResourceAsStream('/' + resource);
			}
		}
		
		return null;
	}

	/*
	 * Get a resource as a URL using the current class path
	 * 
	 * @param resource - the resource to locate
	 * 
	 * @param classLoader - the class loaders to examine
	 * 
	 * @return the resource or null
	 */
	URL getResourceAsURL(String resource, ClassLoader[] classLoader) {

		URL returnValue = null;

		for (ClassLoader cl : classLoader) {

			if (null != cl) {

				// look for the resource as passed in...
				returnValue = cl.getResource(resource);

				// ...but some class loaders want this leading "/", so we'll add
				// it
				// and try again if we didn't find the resource
				if (null == returnValue) {
					returnValue = cl.getResource('/' + resource);
				}

				// "It's always in the last place I look for it!"
				// ... because only an idiot would keep looking for it after
				// finding it, so stop looking already.
				if (null != returnValue) {
					return returnValue;
				}

			}

		}
		
		// try again if we didn't find the resource
		if (null == returnValue) {
			returnValue = ClassLoader.getSystemResource(resource);
			if (null == returnValue) {
				returnValue = ClassLoader.getSystemResource('/' + resource);
			}
		}

		// didn't find it anywhere.
		return null;

	}

	Iterator<URL> getResourceAsURLs(String resource, ClassLoader[] classLoader) throws IOException {

        EnumerationIterator<URL> iterator = new EnumerationIterator<URL>();
        
        Enumeration<URL> returnValue = null;

		for (ClassLoader cl : classLoader) {

			if (null != cl) {

				// look for the resource as passed in...
				returnValue = cl.getResources(resource);
				// ...but some class loaders want this leading "/", so we'll add
				// it
				// and try again if we didn't find the resource
				if (null == returnValue) {
					returnValue = cl.getResources('/' + resource);
				}

				// "It's always in the last place I look for it!"
				// ... because only an idiot would keep looking for it after
				// finding it, so stop looking already.
				if (null != returnValue) {
					iterator.addEnumeration(returnValue);
					return iterator;
				}

			}

		}

		// try again if we didn't find the resource
		if (null == returnValue) {
			returnValue = ClassLoader.getSystemResources(resource);
			if (null == returnValue) {
				returnValue = ClassLoader.getSystemResources('/' + resource);
			}
		}
		
		// didn't find it anywhere.
		return null;
   }
	
	/*
	 * Attempt to load a class from a group of classloaders
	 * 
	 * @param name - the class to load
	 * 
	 * @param classLoader - the group of classloaders to examine
	 * 
	 * @return the class
	 * 
	 * @throws ClassNotFoundException - Remember the wisdom of Judge Smails:
	 * Well, the world needs ditch diggers, too.
	 */
	Class<?> classForName(String name, ClassLoader[] classLoader)
			throws ClassNotFoundException {

		for (ClassLoader cl : classLoader) {

			if (null != cl) {

				try {

					Class<?> c = Class.forName(name, true, cl);

					if (null != c) {
						return c;
					}

				} catch (ClassNotFoundException e) {
					// we'll ignore this until all classloaders fail to locate
					// the class
				}

			}

		}

		throw new ClassNotFoundException("Cannot find class: " + name);

	}

	public ClassLoader[] getClassLoaders(ClassLoader classLoader) {
		return new ClassLoader[] { 
				classLoader, 
				getDefaultClassLoader(),
				getContextClassLoader(),
				getClass().getClassLoader()
			};
	}

	public ClassLoader getDefaultClassLoader() {
		return defaultClassLoader;
	}

	public void setDefaultClassLoader(ClassLoader defaultClassLoader) {
		this.defaultClassLoader = defaultClassLoader;
	}

	public ClassLoader getContextClassLoader() {
		return contextClassLoader;
	}
	

}
